package uva;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;

import rice.Continuation;
import rice.environment.Environment;
import rice.p2p.commonapi.Id;
import rice.p2p.commonapi.Node;
import rice.pastry.NodeHandle;
import rice.pastry.NodeIdFactory;
import rice.pastry.PastryNode;
import rice.pastry.PastryNodeFactory;
import rice.pastry.leafset.LeafSet;
import rice.pastry.socket.SocketPastryNodeFactory;
import rice.pastry.standard.RandomNodeIdFactory;

/**
 * This tutorial shows how to setup a FreePastry node using the Socket Protocol.
 * 
 * @author Jeff Hoye
 */
public class RingRunner {

	// this will keep track of our applications
	Vector<PotentiallyMaliciousApp> apps = new Vector<PotentiallyMaliciousApp>();
	MaliciousNodes maliciousNodes;

	public RingRunner(int bindport, InetSocketAddress bootaddress, int numNodes, Environment env) throws Exception {
		maliciousNodes = MaliciousNodes.getInstance();
		// Generate the NodeIds Randomly
		NodeIdFactory nidFactory = new MaliciousRandomIdFactory(env);
		MaliciousNodeFactory factory = new MaliciousNodeFactory(nidFactory, bindport, env);
		for (int i = 0; i < numNodes; i++) {
			PastryNode node = null;

			if (i % 5 == 0) {
				// System.out.println("malicious node in index: " + i);
				node = factory.newNode(true);
				MaliciousNodes.getInstance().add(node.getLocalHandle());

				System.out.println("creating " + ">!" + node.getId().toStringFull() + "!<");
			}
			else {
				node = factory.newNode(false);
				System.out.println("creating " + node.getId().toStringFull());
			}

			// construct a new MyApp
			PotentiallyMaliciousApp app = new PotentiallyMaliciousApp(node, false);

			apps.add(app);

			node.boot(bootaddress);

			synchronized (node) {
				while (!node.isReady() && !node.joinFailed()) {
					// delay so we don't busy-wait
					node.wait(500);

					// abort if can't join
					if (node.joinFailed()) {
						throw new IOException("Could not join the FreePastry ring.  Reason:" + node.joinFailedReason());
					}
				}
			}
		}

		System.out.println("number of malicious nodes: " + maliciousNodes.size() + " number non malicious nodes: " + (numNodes - maliciousNodes.size()));
		int iteration = 0;
		int currentCount = 0;
		int oldCount = -1;

		GraphViz oldGviz = null;
		GraphViz newGviz = null;
		do {

			iteration++;
			// System.out.println(((PastryNode)apps.get(0).getNode()).getRoutingTable().printSelfFull());

			newGviz = new GraphViz("/home/alagenchev/graph.txt");

			for (PotentiallyMaliciousApp currentApp : apps) {

				// System.out.println(((PastryNode)
				// currentApp.getNode()).getRoutingTable().printSelf());

				// System.out.println("for node: " + ((PastryNode)
				// currentApp.getNode()).getId().toStringFull() + " leafset: " +
				// ((PastryNode) currentApp.getNode()).getLeafSet().toString());

				newGviz.addEdges(currentApp.getNode());
			}
			System.out.print("iteration: " + iteration + "\n");
			// gviz.drawGraph();
			// currentCount = newGviz.calculatePrintMaliciousNodes();

			if (currentCount < oldCount) {
				System.out.println("previous count was less than current count");

				// System.out.println("old gviz edges");
				//
				// oldGviz.printAllEdges();
				//
				// System.out.println("new gviz edges");
				//
				// newGviz.printAllEdges();
				// //printMissingEdges(oldGviz, newGviz);
				//
				// System.out.println("old gviz malicious edges");
				// oldGviz.printAllMaliciousEdges();
				//
				// System.out.println("new gviz malicious edges");
				// newGviz.printAllMaliciousEdges();
				//
				// oldGviz = newGviz;
				// printMissingEdges(newGviz, oldGviz);

				System.exit(0);
			}

			if (newGviz != null) {
				oldGviz = newGviz;
			}
			oldCount = currentCount;


			for (PotentiallyMaliciousApp currentApp : apps) {

				System.out.println("iteration: "+iteration+" main:\n" + ((PastryNode) currentApp.getNode()).getRoutingTable()
						.GetUnderlyingTable(-1).printSelf());
				System.out.println("0:\n" + ((PastryNode) currentApp.getNode()).getRoutingTable().GetUnderlyingTable(0).printSelf());
				System.out.println("1:\n" + ((PastryNode) currentApp.getNode()).getRoutingTable().GetUnderlyingTable(1).printSelf());
				System.out.println("2:\n" + ((PastryNode) currentApp.getNode()).getRoutingTable().GetUnderlyingTable(2).printSelf());

			}

			MaliciousNodes.getInstance().setAreMaliciousNodesActive(true);
			Thread.sleep(10000);
		}
		while (iteration < 5);
		// System.out.println("gviz malicious edges");

		// gviz.printAllMaliciousEdges();

		// MaliciousNodes.getInstance().setAreMaliciousNodesActive(true);
		// Thread.sleep(30000);

		// GraphViz gviz2 = new GraphViz("/home/alagenchev/graph2.txt");

		// gviz2.drawGraph();
		// gviz2.calculatePrintMaliciousNodes();

		// System.out.println("malicous nodes are: " +
		// MaliciousNodes.getInstance().size());
		// System.out.println("gviz2 malicious edges");

		// gviz2.printAllMaliciousEdges();
		

		
		System.exit(0);
	}

	private void printMissingEdges(GraphViz oldGviz, GraphViz newGviz) {

		for (int i = 0; i < oldGviz.GetEdges().size(); i++) {
			if (!oldGviz.checkIfEdgeIsMalicious(oldGviz.GetEdges().get(i))) {
				oldGviz.GetEdges().remove(i);
				i--;
				continue;
			}

			int index = newGviz.hasMaliciousEdge(oldGviz.GetEdges().get(i));
			if (index >= 0) {
				oldGviz.GetEdges().remove(i);
				i--;
			}
			else {
				System.out.println("missing malicious edge:");
				if (oldGviz.GetEdges().get(i).getIsBiDirectional()) {
					System.out.println("\"" + oldGviz.GetEdges().get(i).getSource() + "\" -> \"" + oldGviz.GetEdges().get(i).getDestination() + "\"; -- bidirectional");
				}
				else {
					System.out.println("\"" + oldGviz.GetEdges().get(i).getSource() + "\" -> \"" + oldGviz.GetEdges().get(i).getDestination() + "\";");
				}
			}
		}
	}

	public static void main(String[] args) throws Exception {

		// the number of nodes to use
		int numNodes = 20;

		// the port to use locally
		int myport = 9001;
		int bootport = 9001;

		String ipAddress = "192.168.1.103";

		// Loads pastry settings
		Environment env = new Environment();

		// disable the UPnP setting (in case you are testing this on a NATted
		// LAN)
		env.getParameters().setString("nat_search_policy", "never");

		// build the bootaddress from the command line args
		InetAddress bootaddr = InetAddress.getByName(ipAddress);

		InetSocketAddress bootaddress = new InetSocketAddress(bootaddr, bootport);

		// launch our node!
		RingRunner runner = new RingRunner(myport, bootaddress, numNodes, env);

	}
}

//
// byte[] bytesOfMessage = msg.toString().getBytes("UTF-8");
//
// MessageDigest md = MessageDigest.getInstance("MD5");
// byte[] thedigest = md.digest(bytesOfMessage);
//
// StringBuffer hexString = new StringBuffer();
// for (int i=0;i<thedigest.length;i++) {
// hexString.append(Integer.toHexString(0xFF & thedigest[i]));
// }
//
// sourceApp.sendMyMsgDirect(victimNode.getLocalNodeHandle(),
// hexString.toString());

// System.out.println("source routing table: " +
// ((PastryNode)sourceApp.getNode()).getRoutingTable().printSelf());
// System.out.println("source leaf set: " +
// ((PastryNode)sourceApp.getNode()).getLeafSet().toString());
// sourceApp.routeMyMsg(victimNode.getId());